home *** CD-ROM | disk | FTP | other *** search
/ PsL Monthly 1993 December / PSL Monthly Shareware CD-ROM (December 1993).iso / prgmming / dos / pascal / heap.com / HEAP.DOC < prev    next >
Encoding:
Text File  |  1989-03-11  |  16.2 KB  |  370 lines

  1.               Advanced Heap Management for Turbo Pascal 5.0
  2.                             Version 5.0
  3.                            March 8, 1989
  4.  
  5. Overview
  6. --------------------------------------------------------------------------
  7. Turbo Pascal's heap is one of the most useful and powerful features of the
  8. language. By using the heap, programs can access all 640K of DOS memory in a
  9. completely dynamic fashion. With power comes responsibility, however. Managing
  10. pointers to the heap is one of the trickiest subjects in Pascal. And as time
  11. goes by, the 640K of DOS memory doesn't seem all that big -- accessing even
  12. more memory is desirable.
  13.  
  14. This collection of Turbo Pascal units and utilities offers methods for
  15. managing and extending the heap. It includes the following:
  16.  
  17.   o a patch to TPC.EXE so that the compiler will generate an interrupt to a
  18.     user-supplied routine after dereferencing each pointer.
  19.   o a unit that checks for dereferencing an invalid pointer.
  20.   o a unit that transfers control of the New, Getmem, Dispose, and FreeMem
  21.     procedures to user-supplied routines.
  22.   o a unit that logs various information about the heap to disk at
  23.     convenient points in a program.
  24.  
  25. Even these are just scratching the surface of what's possible. Further
  26. possibilities include: a completely transparent heap manager that uses either
  27. normal RAM, disk or expanded memory for its raw material; and a handle-based
  28. pointer system that allows heap compaction. We may develop these ourselves as
  29. time becomes available, but we'd like to hear about anything that you develop
  30. along these lines in the meantime.
  31.  
  32. This collection of utilities is an updated version of those previously
  33. written for Turbo Pascal 4.0.
  34.  
  35.  
  36. Patching TPC.EXE
  37. --------------------------------------------------------------------------
  38. The patching program HEAPPAT5 makes a small but important change to TPC.EXE
  39. (version 5.0 only). Once the change is in place, the compiler is capable of
  40. generating an interrupt each time a pointer is dereferenced. When the
  41. interrupt occurs, a user-supplied routine takes control, thus allowing a
  42. program to validate or modify the pointer value.
  43.  
  44. To apply the patch, compile the supplied program HEAPPAT5.PAS, and run it while
  45. a copy of TPC.EXE is in the current directory. HEAPPAT5 modifies about 75
  46. bytes of the command line compiler (most of which are required to relocate a
  47. particular table so that it will hold two more bytes). Note that the patcher
  48. works only on TPC, not on TURBO.EXE. While it would be possible to make the
  49. same patch to TURBO.EXE, we rarely use the integrated compiler much ourselves,
  50. and it is linked differently enough to make finding the patch points a
  51. time-consuming chore.
  52.  
  53. HEAPPAT5 assures that the compiler version you have is Turbo Pascal 5.00. If
  54. the file size is wrong, or if any of the locations to be patched contain
  55. unexpected data, HEAPPAT5 will halt with an error message. If this happens, be
  56. sure to restore a clean copy of TPC.EXE from a backup, since a partial patch
  57. may have occurred.
  58.  
  59. After the patch is successfully completed, the compiler's behavior is changed
  60. in the following manner:
  61.  
  62.   0. The compiler version will be reported as "5.0P" whenever TPC writes its
  63.   copyright message.
  64.  
  65.   1. A new compiler switch directive will exist: {$P+} or {$P-}. The default
  66.   state of this switch is {$P-}. When the switch is turned ON, the compiler
  67.   will generate an interrupt $66 after each pointer dereference. The switch
  68.   may be turned on and off as desired in the source code, at the DOS command
  69.   line, or in a TPC.CFG file. Like other Turbo compiler directives, the switch
  70.   returns to its default state at the beginning of each unit.
  71.  
  72.   It is your program's responsibility to install an interrupt handler prior to
  73.   the first occurrence of int $66 in the program. The supplied unit BADPTR
  74.   automatically installs such a handler for you.
  75.  
  76.   Interrupt $66 is one of the user-definable interrupts described by IBM. If
  77.   this interrupt conflicts with your application, you may change HEAPPAT5.PAS
  78.   to use a different interrupt. To do so, simply change the constant
  79.   DerefInterrupt in HEAPPAT5.PAS, recompile the program and patch a fresh copy
  80.   of the compiler. User-definable interrupts range from $60 to $66. ($67 is
  81.   used for EMS, so it should be avoided here.)
  82.  
  83.   If you change the interrupt, be sure to modify any related interrupt handler
  84.   (such as the one in BADPTR) as well.
  85.  
  86.   2. The patch has one negative side effect. The patched compiler will no
  87.   accept the /$M (stack and heap settings) directive at the command line or in
  88.   the TPC.CFG file. The {$M} source directive is available as usual; the
  89.   memory settings should generally be written in the source code anyway.
  90.  
  91.   3. Upon entry to your interrupt handler, ES:DI will hold the current value
  92.   of the pointer being dereferenced. The interrupt handler must preserve the
  93.   values of AX, BX, CX, DX, SI, DS, SS, BP, and SP, and it must return either
  94.   the same value of ES:DI or a value appropriately mapped by a virtual memory
  95.   manager.
  96.  
  97. While a Turbo Pascal Interrupt procedure will work correctly for the pointer
  98. handler, the performance penalty may be unacceptable. For best performance,
  99. the int $66 handler should be written primarily in assembly language. See
  100. BADPTR.ASM for a simple example of such a handler.
  101.  
  102. The following fragment shows the code generated by the patched compiler when a
  103. pointer P is a global variable, in this case a pointer to an integer.
  104.  
  105.                  P^ := 1;
  106.         C43E0000       LES    DI,[P]                ;load pointer into ES:DI
  107.         CD66           INT    66                    ;call user routine
  108.         26C7050100     MOV    WORD PTR ES:[DI],0001 ;assign value
  109.  
  110. An easy rule to remember is this: for each appearance of a dereferencing caret
  111. (^) in your source code, an interrupt will be generated. Of course, this
  112. occurs only when the $P+ directive is in effect for that statement.
  113.  
  114.  
  115. Using BADPTR
  116. --------------------------------------------------------------------------
  117. BADPTR is a unit that works in conjunction with the HEAPPAT5-patched compiler.
  118. If you compile with the patched compiler, you must be sure that you USE the
  119. unit BADPTR (or another unit with an int $66 handler), or your program may
  120. crash unexpectedly. (Interrupt $66 often points to an IRET by default, but
  121. that is not always the case.) You should USE BADPTR early in the USES
  122. statement of the main program. If you USE BADPTR and then don't compile with
  123. the patched compiler, no harm will result.
  124.  
  125. BADPTR automatically installs an interrupt handler that is invoked whenever a
  126. pointer is dereferenced and the $P+ directive is active. This interrupt
  127. handler checks that the pointer refers to the normal Turbo Pascal heap,
  128. between HeapOrg and the top of the free list. If the pointer falls into this
  129. range, the interrupt handler returns and the program proceeds. If the pointer
  130. is outside of the normal heap, BADPTR calls an error routine, which writes the
  131. value of the pointer as well as the relative code address where the error
  132. occurred, and then halts. Note that NIL pointers will always fail BADPTR's
  133. test. You can use Turbo's find runtime error facility to correlate the error
  134. address to a source position.
  135.  
  136. If you are using pointers not allocated by New or GetMem, there are two ways
  137. to keep BADPTR from reporting a false error. A common example of such a
  138. pointer would be one pointing to the DOS command line, initialized with P :=
  139. Ptr(PrefixSeg, $80). First, you could assure that the $P+ directive is not
  140. active wherever you dereference such a pointer. Second, BADPTR interfaces two
  141. WORD variables that allow you to determine the acceptable range for pointers.
  142. HeapBot is the lowest acceptable pointer segment, and HeapTop is the highest.
  143. BADPTR initializes these to the range of the normal Turbo heap. Suppose you
  144. wanted to accept any pointer but the NIL pointer. In that case you could
  145. assign:
  146.  
  147.   HeapBot := $0001;
  148.   HeapTop := $FFFF;
  149.  
  150. BADPTR uses simple WriteLn statements to report an error. If this technique is
  151. not appropriate for a particular application, modify the procedure BadPointer
  152. in BADPTR.PAS. Upon entry to BadPointer, the system variable ErrorAddr
  153. contains the PSP-relative address of the code causing the error, and BadP
  154. contains the faulty pointer. Generally, BadPointer should halt without
  155. returning. If it does not halt, execution will continue after the interrupt,
  156. using the pointer originally supplied in ES:DI. The BadPointer procedure
  157. cannot change the actual pointer value.
  158.  
  159. If you change HEAPPAT5 to use a different interrupt, be sure to make the
  160. corresponding change in BADPTR as well.
  161.  
  162.  
  163. Using GRABHEAP
  164. --------------------------------------------------------------------------
  165. GRABHEAP is a unit that allows a program to take control of memory allocation
  166. and deallocation functions normally handled by the system unit: NEW, GETMEM,
  167. DISPOSE, and FREEMEM. To offer complete control, GRABHEAP interfaces two
  168. procedures:
  169.  
  170.   procedure CustomHeapControl(GetPtr, FreePtr : Pointer);
  171.     {-Give control of GetMem, New, FreeMem, Dispose to specified procedures}
  172.  
  173.   procedure SystemHeapControl;
  174.     {-Restore control to the system heap routines}
  175.  
  176. A program can call CustomHeapControl to transfer control of the system
  177. routines to specified procedures. To do so, pass the addresses of two FAR,
  178. global procedures that match the following declarations:
  179.  
  180.   {$F+}
  181.   procedure CustomGetMem(var P : Pointer; Size : Word);
  182.   begin
  183.     ...
  184.   end;
  185.   procedure CustomFreeMem(var P : Pointer; Size : Word);
  186.   begin
  187.     ...
  188.   end;
  189.   {$F-}
  190.  
  191. To set up for this example, an appropriate call would be
  192.  
  193.   CustomHeapControl(@CustomGetMem, @CustomFreeMem);
  194.  
  195. Thereafter, any calls that would normally go to New or GetMem are transferred
  196. to CustomGetMem, and calls for Dispose or FreeMem are sent to CustomFreeMem.
  197.  
  198. The custom heap management routines can perform any needed actions, including
  199. calls to the original system GetMem and FreeMem routines. To call the original
  200. routines, the program must temporarily restore control to the system runtime
  201. library by calling GRABHEAP's SystemHeapControl routine.
  202.  
  203. Here is an example of CustomGetMem and CustomFreeMem routines that do nothing
  204. but keep a balance sheet of the memory allocated and deallocated in a program:
  205.  
  206.   var
  207.     TotalAlloc : LongInt;
  208.     HeapMax : Pointer;
  209.  
  210.   {$F+}
  211.   procedure MyFree(var P : Pointer; Size : Word); forward;
  212.  
  213.   procedure MyGet(var P : Pointer; Size : Word);
  214.   begin
  215.     Inc(TotalAlloc, Size);              {Update balance sheet}
  216.     SystemHeapControl;                  {Give back heap control temporarily}
  217.     GetMem(P, Size);                    {Use the system routine to allocate}
  218.     if LongInt(HeapPtr) > LongInt(HeapMax) then
  219.       HeapMax := HeapPtr;               {Keep track of heap high water mark}
  220.     CustomHeapControl(@MyGet, @MyFree); {Take over heap control again}
  221.   end;
  222.  
  223.   procedure MyFree(var P : Pointer; Size : Word);
  224.   begin
  225.     Dec(TotalAlloc, Size);
  226.     SystemHeapControl;
  227.     FreeMem(P, Size);
  228.     CustomHeapControl(@MyGet, @MyFree);
  229.   end;
  230.   {$F-}
  231.  
  232.   begin
  233.     TotalAlloc := 0;                           {No memory allocated to start}
  234.     HeapMax := HeapOrg;                        {High water mark at base of heap}
  235.     CustomHeapControl(@MyGet, @MyFree);        {Take over heap control}
  236.  
  237.     ....                                       {Normal program actions}
  238.  
  239.     WriteLn('Maximum heap usage: ',
  240.             16*(LongInt(seg(HeapMax^))-seg(HeapOrg^)), ' bytes');
  241.     if TotalAlloc <> 0 then
  242.       WriteLn('Allocated memory not freed: ', TotalAlloc, ' bytes');
  243.   end.
  244.  
  245. GRABHEAP can be put to more powerful uses, of course. For example, based on an
  246. installation flag, it could select among data storage in normal, EMS, or disk
  247. memory. The pointer returned by the custom GetMem routine need not be a normal
  248. pointer, but can instead be an EMS page and offset, or a disk page and offset,
  249. combined into a four byte record. Then, based on the same installation flag, a
  250. deref-interrupt handler can access the data on the appropriate media and
  251. return a pointer to the actual data buffered in memory.
  252.  
  253.  
  254. Using HEAPLOG
  255. -------------------------------------------------------------------------
  256. HEAPLOG is a unit that builds on top of the GRABHEAP facility to provide
  257. diagnostic information for programs that make extensive use of the heap. It
  258. keeps a log of all heap allocation and deallocation, and allows the program to
  259. dump this log to disk at any time. By studying the log, one can detect
  260. excessive heap fragmentation and memory that hasn't been deallocated.
  261.  
  262. You'll get a basic log just by using HEAPLOG in your program. HEAPLOG creates
  263. a file named HEAP.LOG. When a program first starts, a report labeled "Initial"
  264. is written to HEAP.LOG. When the program ends, a report labeled "Final" is
  265. written to HEAP.LOG. The reports themselves will be described momentarily.
  266.  
  267. HEAPLOG interfaces two procedures that control the logging process:
  268.  
  269. procedure DumpHeapLog(Msg : string);
  270.   {-Write the current heap log to a file}
  271.  
  272. procedure ClearLog;
  273.   {-Clear all entries from the log}
  274.  
  275. DumpHeapLog adds another report to HEAP.LOG, giving it the label passed in
  276. Msg. ClearLog clears HEAPLOG's internal data structures so that succeeding
  277. reports will be relative to that point instead of relative to the beginning of
  278. the program.
  279.  
  280. The following is an example of a HEAPLOG report.
  281.  
  282. Initial
  283.  
  284. MemAvail: 385568
  285. MaxAvail: 385568
  286. HeapPtr : 41DE:0000
  287. HeapCnt : 0
  288. FreeCnt : 0
  289. Filled  : FALSE
  290.  
  291. Intermediate
  292.  
  293. MemAvail: 382017
  294. MaxAvail: 380742
  295. HeapPtr : 4309:0002
  296. HeapCnt : 15
  297. FreeCnt : 5
  298. Filled  : FALSE
  299.  
  300.   Pointer   Size  Allocated at
  301. 42CF:0008    115  0000:0AE3
  302. 42E9:000F    499  0000:0AE3
  303. 41F8:000F    387  0000:07F1
  304. 4229:0004    396  0000:07F1
  305. 4259:0007    125  0000:07F1
  306. 4261:0004    415  0000:07F1
  307. 427B:0003    116  0000:07F1
  308. 4282:0007    269  0000:07F1
  309. 4293:0004    223  0000:07F1
  310. 42A4:0009    158  0000:07F1
  311. 42AE:0007     42  0000:07F1
  312. 42B1:0001    112  0000:07F1
  313. 42B8:0001     74  0000:07F1
  314. 42BC:000B    301  0000:07F1
  315. 42D8:0008    279  0000:07F1
  316.  
  317. Free start  Size
  318. 42D6:000B     29
  319. 42A1:0003     54
  320. 4242:0000    375
  321. 4211:0002    386
  322. 41DE:0000    431
  323.  
  324. Final
  325.  
  326. MemAvail: 385568
  327. MaxAvail: 385568
  328. HeapPtr : 41DE:0000
  329. HeapCnt : 0
  330. FreeCnt : 0
  331. Filled  : FALSE
  332.  
  333. HEAPLOG always generates the Initial and Final reports. For programs that
  334. deallocate all their dynamic memory, the Final report should be the same as
  335. the Initial. In the example, the Intermediate report is one generated by
  336. calling DumpHeapLog directly.
  337.  
  338. Each report shows the values of MemAvail and MaxAvail in bytes, and the
  339. current heap high water mark, HeapPtr. Each also shows the number of separate
  340. blocks allocated on the heap (HeapCnt) and the number of blocks deallocated
  341. and currently on the free list (FreeCnt). If HeapCnt is non-zero, HEAPLOG
  342. shows the value of each allocated pointer, the size of the region it points
  343. to, and the code address where the pointer was allocated. If FreeCnt is
  344. non-zero, HEAPLOG shows the starting address of each free block and its size.
  345.  
  346. By default, HEAPLOG can track the allocation of up to 1000 pointers in one
  347. run. If the program allocates more than this at one time, the Filled field
  348. will report True. HEAPLOG's capacity can be adjusted by modifying the constant
  349. MaxLog in HEAPLOG.PAS. Note that HEAPLOG itself uses 10 bytes of heap space
  350. for each increment in MaxLog. (HEAPLOG doesn't report its own heap usage,
  351. however.) The performance of calls to GetMem, FreeMem, New, and Dispose will
  352. degrade for large numbers of pointers.
  353.  
  354.  
  355. Legalities
  356. --------------------------------------------------------------------------
  357. All programs and documentation in this package are copyright (C) TurboPower
  358. Software, 1988, 1989. All rights reserved. TurboPower Software hereby grants
  359. permission for free distribution of this software, and for use of these units
  360. and techniques within commercial and non-commercial applications. The units
  361. and utilities themselves may not be distributed commercially without obtaining
  362. written permission from TurboPower Software.
  363.  
  364. We would appreciate hearing about enhancements made to these routines. Contact
  365. Kim Kokkonen at Compuserve ID 72457,2131 or write to:
  366.  
  367.   TurboPower Software
  368.   P.O. Box 66747
  369.   Scotts Valley, CA 95066
  370.